/*
   Copyright 20087 Newton Whitman

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */

package com.func4.auth;

/**
 * ContainerLoginModule.java @author newtonw Jul 16, 2007
 * This is a JAAS login module that relies on the db4o object database for user storage.
 * It is designed for servlet containers such as Tomcat.
 */


import java.util.*;
import java.io.IOException;
import javax.security.auth.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import javax.security.auth.spi.*;
import java.security.Principal;
import com.db4o.*;
import com.db4o.query.*;

public class ContainerLoginModule implements LoginModule {

	private Subject subject;
	private CallbackHandler callbackHandler;
	private Map sharedState;
	private Map options;
	private boolean succeeded;
	private boolean commitSucceeded;
	private String username;
	private char[] password;
	private List<Principal> securityRoles;
	private String authHost;
	private int authPort;
	private String authUserName;
	private String authPassword;
	private String userNameField;
	private String passwordField;
	private String userRoleField;

    /* (non-Javadoc)
     * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject, javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map)
     */
    public void initialize(Subject subject, CallbackHandler callbackHandler,
			Map sharedState, Map options) {
		this.setSubject(subject);
		this.setCallbackHandler(callbackHandler);
		this.setSharedState(sharedState);
		this.setOptions(options);
		this.setSucceeded(false);
		this.setCommitSucceeded(false);
		setAuthHost((String)getOptions().get("authHost"));
		setAuthPort(Integer.parseInt((String)getOptions().get("authPort")));
		setAuthUserName((String)getOptions().get("authUserName"));
		setAuthPassword((String)getOptions().get("authPassword"));
		setUserNameField((String)getOptions().get("userNameField"));
		setPasswordField((String)getOptions().get("passwordField"));
		setUserRoleField((String)getOptions().get("userRoleField"));
    }

 
    /* (non-Javadoc)
     * @see javax.security.auth.spi.LoginModule#login()
     */
    public boolean login() throws LoginException {
    	processCallback();
    	if(getUser()){
    		setSucceeded(true);
    	}else {
    		throw new FailedLoginException("Invalid User or Password");
	    }
    	return isSucceeded();
    }

	/**
	 * processCallback
	 * calls back to retrieve user name and password for verification
	 * @throws LoginException void
	 */
	private void processCallback() throws LoginException{
		if (getCallbackHandler() == null){
		    throw new LoginException("Error: no CallbackHandler available");
		}
		Callback[] callbacks = new Callback[2];
		callbacks[0] = new NameCallback("user name: ");
		callbacks[1] = new PasswordCallback("password: ", false);
		try {
		    getCallbackHandler().handle(callbacks);
		    setUsername(((NameCallback)callbacks[0]).getName());
		    char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword();
		    if (tmpPassword == null) {// treat a NULL password as an empty password
		    	tmpPassword = new char[0];
		    }
		    setPassword(tmpPassword);
		} catch (java.io.IOException ioe) {
		    throw new LoginException(ioe.toString());
		} catch (UnsupportedCallbackException uce) {
		    throw new LoginException("Error: " + uce.getCallback().toString());
		}
	}
	
	/**
	 * getUser
	 * retrieves a user from db4o database
	 * this is done by interface and using 
	 * options from JAAS LoginConfig file to specify db info
	 * @param userName
	 * @return AuthUser
	 * @throws LoginException AuthUser
	 */
	private boolean getUser() throws LoginException{
		ObjectContainer db = null;
		boolean success = false;
		if(getUsername() != null){
    		try{
    			db = Db4o.openClient(getAuthHost(), getAuthPort(), getAuthUserName(), getAuthPassword());
    			Query userQuery = db.query();
    			userQuery.constrain(com.func4.auth.AuthUser.class);
    			userQuery.descend(getUserNameField()).constrain(getUsername());
    			userQuery.descend(getPasswordField()).constrain(String.valueOf(getPassword()));
    			Query rolesQuery = userQuery.descend(getUserRoleField()); 
    			ObjectSet results = rolesQuery.execute();
    			if(results.hasNext()){
	    			ArrayList<Principal> roles = new ArrayList<Principal>();
	    			roles.add(new UserPrincipal(getUsername()));
	    			while(results.hasNext()){
	    				roles.add((Principal)results.next());
	    			}
	    			setSecurityRoles(roles);
					success = true;
    			}
    			
    		} catch(IOException e){
    			throw new LoginException(e.toString());
    		}
    	    finally{
    	    	if(db != null){
    	    		db.close();
    	    	}
    	    }
    	}
		return success;
	}
	
    /* (non-Javadoc)
     * @see javax.security.auth.spi.LoginModule#commit()
     */
    public boolean commit() throws LoginException {
    	if(isSucceeded()){
	    	for(Principal role:getSecurityRoles()){
	    		if(!getSubject().getPrincipals().contains(role)){
	    			getSubject().getPrincipals().add(role);
	    		}
		    }
	    	setCommitSucceeded(true);
    	}
    	clearState();
	    return isCommitSucceeded();
    }
    
    /**
     * clearState
     */
    private void clearState(){
    	setUsername(null);
    	Arrays.fill(getPassword(), ' ');
    	setPassword(null);
    }

    /* (non-Javadoc)
     * @see javax.security.auth.spi.LoginModule#abort()
     */
    public boolean abort() throws LoginException {
    	boolean aborted = false;
		if (isSucceeded() == true){
			aborted = true;
			if(isCommitSucceeded()){
				logout();
			}
		} 
		clearState();
		return aborted;
    }

    /* (non-Javadoc)
     * @see javax.security.auth.spi.LoginModule#logout()
     */
    public boolean logout() throws LoginException {
		for(Principal role:getSecurityRoles()){
    		if(getSubject().getPrincipals().contains(role)){
    			getSubject().getPrincipals().remove(role);
    		}
	    }
		return true;
    }


	/**
	 * @param subject the subject to set
	 */
	private void setSubject(Subject subject) {
		this.subject = subject;
	}


	/**
	 * @return the subject
	 */
	private Subject getSubject() {
		return subject;
	}


	/**
	 * @param callbackHandler the callbackHandler to set
	 */
	private void setCallbackHandler(CallbackHandler callbackHandler) {
		this.callbackHandler = callbackHandler;
	}


	/**
	 * @return the callbackHandler
	 */
	private CallbackHandler getCallbackHandler() {
		return callbackHandler;
	}


	/**
	 * @param sharedState the sharedState to set
	 */
	private void setSharedState(Map sharedState) {
		this.sharedState = sharedState;
	}


	/**
	 * @return the sharedState
	 */
	private Map getSharedState() {
		return sharedState;
	}


	/**
	 * @param options the options to set
	 */
	private void setOptions(Map options) {
		this.options = options;
	}


	/**
	 * @return the options
	 */
	private Map getOptions() {
		return options;
	}


	/**
	 * @param succeeded the succeeded to set
	 */
	private void setSucceeded(boolean succeeded) {
		this.succeeded = succeeded;
	}


	/**
	 * @return the succeeded
	 */
	private boolean isSucceeded() {
		return succeeded;
	}


	/**
	 * @param commitSucceeded the commitSucceeded to set
	 */
	private void setCommitSucceeded(boolean commitSucceeded) {
		this.commitSucceeded = commitSucceeded;
	}


	/**
	 * @return the commitSucceeded
	 */
	private boolean isCommitSucceeded() {
		return commitSucceeded;
	}


	/**
	 * @param username the username to set
	 */
	private void setUsername(String username) {
		this.username = username;
	}


	/**
	 * @return the username
	 */
	private String getUsername() {
		return username;
	}


	/**
	 * @param password the password to set
	 */
	private void setPassword(char[] password) {
		this.password = password;
	}


	/**
	 * @return the password
	 */
	private char[] getPassword() {
		return password;
	}

	/**
	 * @param securityRoles the securityRoles to set
	 */
	private void setSecurityRoles(List<Principal> securityRoles) {
		this.securityRoles = securityRoles;
	}


	/**
	 * @return the securityRoles
	 */
	private List<Principal> getSecurityRoles() {
		return securityRoles;
	}


	/**
	 * setAuthHost
	 * @param authHost the authHost to set
	 */
	private void setAuthHost(String authHost) {
		this.authHost = authHost;
	}


	/**
	 * getAuthHost
	 * @return the authHost
	 */
	private String getAuthHost() {
		return authHost;
	}


	/**
	 * setAuthPort
	 * @param authPort the authPort to set
	 */
	private void setAuthPort(int authPort) {
		this.authPort = authPort;
	}


	/**
	 * getAuthPort
	 * @return the authPort
	 */
	private int getAuthPort() {
		return authPort;
	}


	/**
	 * setAuthUserName
	 * @param authUserName the authUserName to set
	 */
	private void setAuthUserName(String authUserName) {
		this.authUserName = authUserName;
	}


	/**
	 * getAuthUserName
	 * @return the authUserName
	 */
	private String getAuthUserName() {
		return authUserName;
	}


	/**
	 * setAuthPassword
	 * @param authPassword the authPassword to set
	 */
	private void setAuthPassword(String authPassword) {
		this.authPassword = authPassword;
	}


	/**
	 * getAuthPassword
	 * @return the authPassword
	 */
	private String getAuthPassword() {
		return authPassword;
	}


	/**
	 * setUserNameField
	 * @param userNameField the userNameField to set
	 */
	private void setUserNameField(String userNameField) {
		this.userNameField = userNameField;
	}


	/**
	 * getUserNameField
	 * @return the userNameField
	 */
	private String getUserNameField() {
		return userNameField;
	}


	/**
	 * setPasswordField
	 * @param passwordField the passwordField to set
	 */
	private void setPasswordField(String passwordField) {
		this.passwordField = passwordField;
	}


	/**
	 * getPasswordField
	 * @return the passwordField
	 */
	private String getPasswordField() {
		return passwordField;
	}


	/**
	 * setUserRoleField
	 * @param userRoleField the userRoleField to set
	 */
	private void setUserRoleField(String userRoleField) {
		this.userRoleField = userRoleField;
	}


	/**
	 * getUserRoleField
	 * @return the userRoleField
	 */
	private String getUserRoleField() {
		return userRoleField;
	}
}
